#include "ft6x36.h"
#include "ft6x36_upgrade_firmware.h"

#if USE_FT6X36

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "../../board.h"
#include "../common/i2c.h"
#include "../common/lv_pm.h"
#include "ft6x36.h"
#include "lvgl/src/lv_core/lv_group.h"
#include "lvgl/src/lv_misc/lv_tsrb.h"
#if USE_TOUCH_TRACKER
#include "touch_tracker.h"
#endif

#define VK_X 120
#define VK_Y 300

static uint16_t ft6x36_reset(int rst_pin);

static const struct ft6x36_info *g_info;

static int vk_state = LV_INDEV_STATE_REL;

#ifdef FT6X36_USE_OSA_HISR

#define POINT_READ_INTERVAL 3 /* tp default int freq is 60Hz, lvgl input dev polling freq is 20Hz */
#define TP_DATA_BYTE 5
#define TP_DATA_BUF_SIZE (256) /* must be power of 2 */

static char tp_data_buf[TP_DATA_BUF_SIZE];
static void *ft6x36_hisr_ref;
static tsrb_t tp_rb;
static int ft_irq_cnt, ft_read_cnt, ft_i2c_cnt, ft_add_cnt, ft_press_cnt, ft_i2c_err;

void ft6x36_disable_irq(void)
{
    gpio_enable_edge_detect(g_info->irq_pin, 0);
}

void ft6x36_enable_irq(void)
{
    gpio_enable_edge_detect(g_info->irq_pin, EDGE_FLAG_FALL);
}

static void ft6x36_irq_handle(void)
{
    ft_irq_cnt++;
    OS_Activate_HISR(&ft6x36_hisr_ref);
}

static int add_point_state(char *buf)
{
    static char last_state = 0;
    unsigned x, y;

    if (!tsrb_empty(&tp_rb) && (last_state == buf[0])){
        return 0;
    }

    if (last_state != buf[0]) {
        ft_press_cnt++;
    }

    last_state = buf[0];

/*
    static int drop_cnt;
    unsigned state;
    state = (buf[0] != 0) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
*/
    x = buf[2] | ((buf[1] & 0xF) << 8);
    y = buf[4] | ((buf[3] & 0xF) << 8);

    if (x > (240 - 1) || y > 300) {
        printf("bad (%u, %u)\n", x, y);
        goto out;
    }

    if ((TP_DATA_BUF_SIZE - tsrb_avail(&tp_rb)) < TP_DATA_BYTE) {
        ;//printf("D: %d, %d, %d, %d\n", x, y, state, drop_cnt++);
    } else {
        tsrb_add(&tp_rb, buf, TP_DATA_BYTE);
        ft_add_cnt++;
        //printf("A: %d, %d, %d, %d\n", x, y, state, lv_tick_get());
#if USE_TOUCH_TRACKER
        touch_tracker_data data;
        data.x = x;
        data.y = y;
        data.sub_status = (buf[0] != 0) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
        touch_tracker_record((void *)&data);
#endif
    }

out:
    return 0;
}

static int state_last = 0;
static int get_point_state(lv_coord_t *x, lv_coord_t *y, lv_indev_state_t *state)
{
    char buf[TP_DATA_BYTE];
    static int x_last = 0, y_last = 0;

    if (tsrb_empty(&tp_rb)) {
        *x = x_last;
        *y = y_last;
        *state = state_last;
#if USE_TOUCH_TRACKER
        return 1;
#endif
    } else {
        tsrb_get(&tp_rb, buf, TP_DATA_BYTE);
        *x = buf[2] | ((buf[1] & 0xF) << 8);
        *y = buf[4] | ((buf[3] & 0xF) << 8);
        *state = (buf[0] != 0) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
        x_last = *x;
        y_last = *y;
        state_last = *state;
        ft_read_cnt++;
        //printf("R: %d, %d, %d, %d\n", *x, *y, *state, lv_tick_get());
    }

    return 0;
}

static void ft6x36_hisr(void)
{
    char buf[TP_DATA_BYTE];
    int ret;

    gpio_enable_edge_detect(g_info->irq_pin, 0);
    ft_i2c_cnt++;

    ret = i2c_read_regs(I2C_MASTER_CI2C1, FT6X36_I2C_ADDR, 0x2, &buf[0], 5, 0);

    if (ret < 0) {
        ft_i2c_err++;
        ft6x36_reset(g_info->rst_pin);
        goto out;
    }

    add_point_state(buf);

out:
    gpio_enable_edge_detect(g_info->irq_pin, EDGE_FLAG_FALL);
    return;
}

bool ft6x36_read(lv_indev_drv_t * indev_drv, lv_indev_data_t *data)
{
    (void) indev_drv;      /*Unused*/

#if USE_TOUCH_TRACKER
    if(get_point_state(&(data->point.x), &(data->point.y), &(data->state)) == 1) {
        touch_tracker_read(indev_drv, data);
    }
#else
    get_point_state(&(data->point.x), &(data->point.y), &(data->state));
#endif
    if ((data->point.x == VK_X) && (data->point.y == VK_Y)) {
            vk_state = data->state;
    }

    return false; /* no more data to read */
}

#else
//!FT6X36_USE_OSA_HISR

static int new_data;

void ft6x36_irq_handle(void)
{
    new_data = 1;
}

bool ft6x36_read(lv_indev_drv_t * indev_drv, lv_indev_data_t *data)
{
    unsigned irq_state;
    unsigned char buf[5];
    int update = 0;
    static int x_last, y_last;

    irq_state = irq_disable();
    if (new_data) {
        new_data = 0;
        update = 1;
    }
    irq_restore(irq_state);

    if (update) {
        i2c_read_regs(I2C_MASTER_CI2C1, FT6X36_I2C_ADDR, 0x2, &buf[0], 5, 0);
        data->point.x = buf[2] | ((buf[1] & 0xF) << 8);
        data->point.y = buf[4] | ((buf[3] & 0xF) << 8);
        x_last = data->point.x;
        y_last = data->point.y;
        data->state = (buf[0] != 0) ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
        // printf("(%d, %d), %s\n", data->point.x, data->point.y,
        // (data->state == LV_INDEV_STATE_PR)?"pressed":"released");
        if ((data->point.x == VK_X) && (data->point.y == VK_Y)) {
            vk_state = data->state;
            // printf("vk %s\n", (vk_state == LV_INDEV_STATE_PR)?"press":"release");
        }
    }
    else {
        data->point.x = x_last;
        data->point.y = y_last;
        data->state = LV_INDEV_STATE_REL;
    }

    return false; /* no more data to read */
}

#endif

bool ft6x36_vk_read(lv_indev_data_t *data)
{
    data->key = LV_KEY_ESC;
    data->state = vk_state;
    // printf("vk: %s\n", (data->state == LV_INDEV_STATE_REL)?"release":"press");
    return false; /* no more data to read */
}

#define RETRY_TIME 3
static uint16_t ft6x36_reset(int rst_pin)
{
    int cnt;
    unsigned char buf[2] = { 0 };

    gpio_output_set(rst_pin, 0);
    uos_sleep(4);/* 20 ms */
    gpio_output_set(rst_pin, 1);
    uos_sleep(40);/* 200ms */

    i2c_initialize(I2C_MASTER_CI2C1);
    cnt = 0;
    while (cnt < RETRY_TIME) {
        i2c_read_reg(I2C_MASTER_CI2C1, FT6X36_I2C_ADDR, FTS_REG_CHIP_ID, &buf[0], 0);
        i2c_read_reg(I2C_MASTER_CI2C1, FT6X36_I2C_ADDR, FTS_REG_CHIP_ID2, &buf[1], 0);
        if ((buf[0] != 0) && (buf[1] != 0)) {
            break;
        }
        else {
            uos_sleep(20); /* 100 ms */
            cnt++;
        }
    }
    if (cnt == RETRY_TIME) {
        printf("ft6x36_reset failed\n");
        return 0;
    }

    return (buf[0] | (buf[1]<<8));
}

static void ft6x36_suspend(void *arg)
{
    struct ft6x36_info *info = arg;
    printf("ft6x36_suspend\n");
    gpio_enable_edge_detect(info->irq_pin, 0);

    i2c_write_reg(I2C_MASTER_CI2C1, FT6X36_I2C_ADDR, FTS_REG_POWER_MODE, FTS_REG_POWER_MODE_SLEEP_VALUE, 0);
#if USE_TOUCH_TRACKER
    touch_tracker_Suspend(NULL);
#endif
}

static void ft6x36_resume(void *arg)
{
    printf("ft6x36_resume\n");
    struct ft6x36_info *info = arg;

    if(ft6x36_reset(info->rst_pin) == 0){
        printf("ft6x36_resume failed\n");
        return;
    }

#ifdef FT6X36_USE_OSA_HISR
    tsrb_init(&tp_rb, tp_data_buf, TP_DATA_BUF_SIZE);
    printf("ft irq cnt = %d, i2c cnt = %d, add cnt = %d, read cnt = %d, press_cnt = %d, i2c_err = %d\n",
        ft_irq_cnt, ft_i2c_cnt, ft_add_cnt, ft_read_cnt, ft_press_cnt, ft_i2c_err);
    ft_irq_cnt = ft_i2c_cnt = ft_read_cnt =ft_add_cnt = ft_press_cnt = 0;
    state_last = 0;
#endif
    gpio_enable_edge_detect(info->irq_pin, EDGE_FLAG_FALL);
#if USE_TOUCH_TRACKER
    touch_tracker_Resume(NULL);
#endif

}

int ft6x36_init(const struct ft6x36_info *info)
{
    gpio_set_direction(info->irq_pin, GPIO_IN);
    gpio_set_direction(info->rst_pin, GPIO_OUT);
    /* reset chip */
    uint16_t res = ft6x36_reset(info->rst_pin);
    if(res == 0) {
        printf("ft6x36_init failed\n");
        return -1;
    }

    printf("Found TP FT6X36: 0x%x\n", res);

    gpio_register_int_handler(info->irq_pin, ft6x36_irq_handle);
#ifdef FT6X36_USE_OSA_HISR
    tsrb_init(&tp_rb, tp_data_buf, TP_DATA_BUF_SIZE);
    OS_Create_HISR(&ft6x36_hisr_ref, "ft6x36_hisr", ft6x36_hisr, HISR_PRIORITY_2);
#endif
    gpio_enable_edge_detect(info->irq_pin, EDGE_FLAG_FALL);

    lv_pm_info_t pm_info;
    pm_info.suspend = ft6x36_suspend;
    pm_info.resume = ft6x36_resume;
    pm_info.data = (void *)info;
    pm_info.part = LV_PM_PART_TOUCH;
    lv_pm_register(&pm_info);

    g_info = info;

#ifdef USE_WATCH_LITE
    extern int fts_fwupg_upgrade(struct fts_upgrade *upg);
    fts_fwupg_work();
#endif

#if USE_TOUCH_TRACKER
    touch_tracker_init();
#endif

    return 0;
}
#endif /* USE_FT6X36 */
